﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using BEPUphysics.Entities;
using BEPUphysics.Entities.Prefabs;

namespace MagicArenaFramework.Camera
{
    public class SimpleCamera : ICamera
    {
        public CameraMode Mode
        {
            get
            {
                return _Mode;
            }
            set
            {
                _Mode = value;
            }
        }
        private CameraMode _Mode;


        private Vector3 position;
        private Vector3 desiredPosition;
        private Vector3 target;
        private Vector3 desiredTarget;
        private Vector3 offsetDistance;

        private float yaw, pitch, roll;
        private float speed;

        private Matrix cameraRotation;
        private Matrix _View, _Projection;

        public Matrix ViewMatrix
        {
            get { return _View; }
        }

        public Matrix ProjectionMatrix
        {
            get { return _Projection; }
        }
        public Vector3 Position
        {
            get { return position; }
            set { position = value; }
        }
        public float Yaw { 
            get { return yaw; }
            set { yaw = value; }
        }
        public float Pitch { 
            get { return pitch; }
            set { pitch = value; }
        }
        public float Roll { 
            get { return roll; }
            set { roll = value; }
        }

        public SimpleCamera()
        {
            Reset();
        }

        public void Reset()
        {
            position = new Vector3(0, 0, 50);
            desiredPosition = position;
            target = new Vector3();
            desiredTarget = target;

            // TODO allow mouse to control offsetDistance
            offsetDistance = new Vector3(0, 15, 40);

            yaw = 0.0f;
            pitch = 0.0f;
            roll = 0.0f;

            speed = .3f;

            cameraRotation = Matrix.Identity;
            _View = Matrix.Identity;
            _Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), 16 / 9, .5f, 500f);       
        }

        //TODO Add mouse control for orbit and free look, may be as simple as changing cam mode on mouse click
        public void Update(Matrix chasedObjectsWorld)
        {
            HandleInput();
            UpdateViewMatrix(chasedObjectsWorld);
        }

        private void HandleInput()
        {
            KeyboardState keyboardState = Keyboard.GetState();

            //Rotate Camera
            if (keyboardState.IsKeyDown(Keys.J))
            {
                if (_Mode != CameraMode.Chase)
                {
                    yaw += .02f;
                }
                else
                {
                    //This is to make the panning more prominent in the Chase Camera.
                    yaw += .2f;
                }
            }
            if (keyboardState.IsKeyDown(Keys.L))
            {
                if (_Mode != CameraMode.Chase)
                {
                    yaw += -.02f;
                }
                else
                {
                    yaw += -.2f;
                }
            }
            if (keyboardState.IsKeyDown(Keys.I))
            {
                if (_Mode != CameraMode.Chase)
                {
                    pitch += -.02f;
                }
                else
                {
                    pitch += .2f;
                }
            }
            if (keyboardState.IsKeyDown(Keys.K))
            {
                if (_Mode != CameraMode.Chase)
                {
                    pitch += .02f;
                }
                else
                {
                    pitch += -.2f;
                }
            }
            if (keyboardState.IsKeyDown(Keys.U))
            {
                roll += -.02f;
            }
            if (keyboardState.IsKeyDown(Keys.O))
            {
                roll += .02f;
            }

            //Move Camera
            if (_Mode == CameraMode.Chase)
            {
                if (keyboardState.IsKeyDown(Keys.W))
                {
                    MoveCamera(cameraRotation.Forward);
                }
                if (keyboardState.IsKeyDown(Keys.S))
                {
                    MoveCamera(-cameraRotation.Forward);
                }
                if (keyboardState.IsKeyDown(Keys.A))
                {
                    MoveCamera(-cameraRotation.Right);
                }
                if (keyboardState.IsKeyDown(Keys.D))
                {
                    MoveCamera(cameraRotation.Right);
                }
                if (keyboardState.IsKeyDown(Keys.E))
                {
                    MoveCamera(cameraRotation.Up);
                }
                if (keyboardState.IsKeyDown(Keys.Q))
                {
                    MoveCamera(-cameraRotation.Up);
                }
            }            
        }

        private void MoveCamera(Vector3 addedVector)
        {
            position += speed * addedVector;
        }

        private void UpdateViewMatrix(Matrix chasedObjectsWorld)
        {
            switch (_Mode)
            {
                case CameraMode.Free:

                    cameraRotation.Forward.Normalize();
                    cameraRotation.Up.Normalize();
                    cameraRotation.Right.Normalize();

                    cameraRotation *= Matrix.CreateFromAxisAngle(cameraRotation.Right, pitch);
                    cameraRotation *= Matrix.CreateFromAxisAngle(cameraRotation.Up, yaw);
                    cameraRotation *= Matrix.CreateFromAxisAngle(cameraRotation.Forward, roll);

                    yaw = 0.0f;
                    pitch = 0.0f;
                    roll = 0.0f;

                    target = position + cameraRotation.Forward;
                    
                    break;

                case CameraMode.Chase:

                    cameraRotation.Forward.Normalize();
                    chasedObjectsWorld.Right.Normalize();
                    chasedObjectsWorld.Up.Normalize();

                    cameraRotation = Matrix.CreateFromAxisAngle(cameraRotation.Forward, roll);
                    
                    desiredTarget = chasedObjectsWorld.Translation;
                    target = desiredTarget;

                    target += chasedObjectsWorld.Right * yaw;
                    target += chasedObjectsWorld.Up * pitch;
                    
                    desiredPosition = Vector3.Transform(offsetDistance, chasedObjectsWorld);
                    position = Vector3.SmoothStep(position, desiredPosition, .15f);
                    
                    yaw = MathHelper.SmoothStep(yaw, 0f, .1f);
                    pitch = MathHelper.SmoothStep(pitch, 0f, .1f);
                    roll = MathHelper.SmoothStep(roll, 0f, .2f);

                    break;

                case CameraMode.Orbit:

                    cameraRotation.Forward.Normalize();
                    
                    cameraRotation = Matrix.CreateRotationX(pitch) * Matrix.CreateRotationY(yaw) * Matrix.CreateFromAxisAngle(cameraRotation.Forward, roll);

                    desiredPosition = Vector3.Transform(offsetDistance, cameraRotation);
                    desiredPosition += chasedObjectsWorld.Translation;
                    position = desiredPosition;

                    target = chasedObjectsWorld.Translation;

                    roll = MathHelper.SmoothStep(roll, 0f, .2f);                                                         

                    break;
            }

            //We'll always use this line of code to set up the View Matrix.
            _View = Matrix.CreateLookAt(position, target, cameraRotation.Up);
        }

        private Entity ChasedEntity = new Box(Vector3.Zero, 1, 1, 1);
        public void SetChasedEntity(BEPUphysics.Entities.Entity entity)
        {
            ChasedEntity = entity;
        }

        public void Update()
        {
            Update(ChasedEntity.WorldTransform);
        }
    }
}
